using System;
using System.Runtime.InteropServices;
using Elevation.Common.Services.Helpers;

namespace Elevation.Common.Services
{
	public static class UserService
	{
		#region Constants

		private const uint SecurityBuiltinDomainRid = 0x00000020;
		private const uint DomainAliasRidAdmins = 0x00000220;
		private const uint TokenQuery = 0x0008;

		private const int ErrorNoSuchLogonSession = 1312;

		#endregion Constants

		#region Extern

		[DllImport("advapi32.dll", SetLastError = true)]
		[return: MarshalAs(UnmanagedType.Bool)]
		private static extern bool AllocateAndInitializeSid(
				ref SidIdentifierAuthority pIdentifierAuthority, byte nSubAuthorityCount,
				uint dwSubAuthority0, uint dwSubAuthority1, uint dwSubAuthority2, uint dwSubAuthority3,
				uint dwSubAuthority4, uint dwSubAuthority5, uint dwSubAuthority6, uint dwSubAuthority7,
				out SafeSidHandler pSid);


		[DllImport("advapi32.dll", SetLastError = true)]
		[return: MarshalAs(UnmanagedType.Bool)]
		private static extern bool CheckTokenMembership(
			IntPtr tokenHandle, SafeSidHandler sidToCheck, [MarshalAs(UnmanagedType.Bool)] out bool isMember);

		[DllImport("advapi32.dll", SetLastError = true)]
		[return: MarshalAs(UnmanagedType.Bool)]
		private static extern bool GetTokenInformation(
				SafeProcessHandle tokenHandle, TokenInformationClass tokenInformationClass, ref IntPtr tokenInformation,
				uint tokenInformationLength, out uint returnLength);

		/// <summary>
		/// No need to close a pseudohandle here. IntPtr is sufficient.
		/// </summary>
		/// <returns></returns>
		[DllImport("kernel32.dll", SetLastError = true)]
		private static extern IntPtr GetCurrentProcess();

		[DllImport("advapi32.dll", SetLastError = true)]
		[return: MarshalAs(UnmanagedType.Bool)]
		private static extern bool OpenProcessToken(IntPtr processHandle, UInt32 desiredAccess, out SafeProcessHandle tokenHandle);

		#endregion Extern

		#region Methods

		public static bool IsAdmin()
		{
			var ntAuthority = new SidIdentifierAuthority(new byte[] { 0, 0, 0, 0, 0, 5 });
			SafeSidHandler adminGroup;
			var result = AllocateAndInitializeSid(
				ref ntAuthority, 2, SecurityBuiltinDomainRid, DomainAliasRidAdmins,
				0, 0, 0, 0, 0, 0, out adminGroup);
			if (!result)
			{
				throw new ApplicationException(
					string.Format("Error {0} while creating administrator's group SID", Marshal.GetLastWin32Error()));
			}

			using (adminGroup)
			{
				bool isAdmin;
				result = CheckTokenMembership(IntPtr.Zero, adminGroup, out isAdmin);
				if (!result)
				{
					throw new ApplicationException(
						string.Format("Error {0} while checking token membership", Marshal.GetLastWin32Error()));
				}

				if (isAdmin)
				{
					return true;
				}
				// If we are on XP or something older.
				if (Environment.OSVersion.Version.Major < 6)
				{
					return false;
				}

				// We are on Vista or something newer.

				var hProcess = GetCurrentProcess();
				if (hProcess == IntPtr.Zero)
				{
					throw new ApplicationException(
						string.Format("Error {0} while getting current process handle", Marshal.GetLastWin32Error()));
				}

				SafeProcessHandle processToken;
				result = OpenProcessToken(hProcess, TokenQuery, out processToken);
				if (!result)
				{
					throw new ApplicationException(
						string.Format("Error {0} while opening process token", Marshal.GetLastWin32Error()));
				}

				using (processToken)
				{
					var linkedToken = IntPtr.Zero;
					uint length;
					result = GetTokenInformation(
						processToken, TokenInformationClass.LinkedToken, ref linkedToken, (uint)IntPtr.Size, out length);
					if (!result)
					{
						var errorCode = Marshal.GetLastWin32Error();
						if (errorCode == ErrorNoSuchLogonSession)
						{
							return false;
						}

						throw new ApplicationException(string.Format("Error {0} while getting token information", errorCode));
					}

					result = CheckTokenMembership(linkedToken, adminGroup, out isAdmin);
					if (!result)
					{
						throw new ApplicationException(
							string.Format("Error {0} while checking token membership", Marshal.GetLastWin32Error()));
					}

					return isAdmin;
				}
			}
		}

		#endregion Methods
	}
}